<html>
<head>
	<title>CSV Loader</title>
</head>
<body style="font-family:consolas;font-size:.8em;" onLoad="resetValidation();">
	<form onSubmit="doNewRequest();"></form>
	<div style="font-family:verdana;">
	<h1>CSV Loader</h1>
	<p>This page will process CSV data which includes a header row.  Escaped commas are not supported.  The GUID of an item Specfication is required.</p>
	</div>
	<table width="100%" style="background-color:#eee;padding:8px;">
	<tr>
	<td>Destination Folder <input id="destinationFolder" style="width:250px;" value="http://inventory.mt.gov/ops/"/></td>
	</tr>
	<tr>
	<td>Specification GUID <input id="specificationId" style="width:250px;" value=""/><button onClick="getSpecificationTypes();" id="specButton" value="Get Specifications">Get Specs</button></td>
	</tr>
	<tr>
	<td><textarea id="csv" style="width: 100%; height: 400px;" onChange="resetValidation()"></textarea></td>
	</tr>
	</table>
	<br />
<button onClick="doValidation();" id="validateButton" value="Validate">Validate</button> - <button onClick="doUploadRequest();" id="uploadButton" value="Upload">Upload</button>
	<br /><br/>
	Response<br />
	<textarea id="output" style="width: 90%; height: 200px;"></textarea>
	<br />
	<button onclick="clearOutput()">
		Clear output</button>
</body>
<script type="text/javascript">

	var output = document.getElementById("output");
	var specCheck = RegExp(/^[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$/);
	// (A-Z, a-z, 0-9, "!" | "*" | "'" | "(" | ")" | "," | "$" | "-" | "_" | "." | "+")
	var nameCheck = RegExp(/^[0-9a-zA-Z\!\*\'\(\)\,\$\-\_\.\+]+$/);
	var nameIndex = -1;
		
	function clearOutput() {
		output.innerHTML = "";
	}
	
	function resetValidation() {
		document.getElementById("validateButton").disabled = false;
		document.getElementById("uploadButton").disabled = true;
	}
	
	/*
	
	List of Basic Properties.

	"ID" - not supported
	"VERSION" - not supported
	"CREATEDBY" not supported
	"PERMISSIONS" not supported

	
	"EXTERNALID" - text
	"PARENTID" - guid
	"LEFTVERSION" - int 
	"RIGHTVERSION" - int
	"PUBLISHED" true/false
	"MIMETYPE" text
	"SPECIFICATION" guid

	"NAME" limited text
	"DISPLAYNAME" limited text

	"RELATIONSHIPS" not currently offered
	
	all other properties are considered 'extended' and should be defined on the item specfication.
	If the item specification says unknown properties are not allowed then trying to add one will
	fail.  In most cases though this option has not been set and adding an unknown property will be
	seamless.
	
	*/
	
	function isNotSettableProperty(propName) {
		propName = propName.toUpperCase();
		
		return (propName == "ID" || propName == "VERSION" || propName == "CREATEDBY" || propName == "PERMISSIONS" || propName == "RELATIONSHIPS");
	}
	
	function isBasicProperty(propName) {
		propName = propName.toUpperCase();
		
		return (propName == "EXTERNALID" || propName == "PARENTID" || propName == "LEFTVERSION" || propName == "RIGHTVERSION" || propName == "PUBLISHED"
			|| propName == "MIMETYPE" || propName == "SPECIFICATION" || propName == "NAME" || propName == "RELATIONSHIPS");
	}
	
	function getSpecificationTypes() {
		var req = new XMLHttpRequest();
		try {
			req.open('REPORT', document.getElementById("destinationFolder").value, false);
			req.send('<?xml version="1.0" encoding="utf-8" ?><mc:Resource-Providers xmlns:mc="uri://MetaCollection/v2" />');
		} catch(err) {
			req.open('PROPFIND', document.getElementById("destinationFolder").value, false);
			req.setRequestHeader('IEISPOOP', 'REPORT'); // IE believes that it knows which verbs a server will support and pukes on REPORT without even trying.
			req.send('<?xml version="1.0" encoding="utf-8" ?><mc:Resource-Providers xmlns:mc="uri://MetaCollection/v2" />');
		}
		alert(req.responseText);
	}
	
	/*
	
	<?xml version="1.0" encoding="utf-8" ?>
	<D:propertyupdate xmlns:D="DAV:" xmlns:mc="uri://MetaCollection/v2" xmlns:mcs="uri://MetaCollection/v2/spec/06c37927-cfec-4624-9b0d-87066d6df559">
		<D:set>
			<D:prop>
				<d:displayname>3490A2047482</d:displayname>
				<mc:externalid></mc:externalid>
				<mc:parentid>ded76a8b-4cf3-4d89-b501-cae3cf846f93</mc:parentid>
				<mc:mimetype>text/html</mc:mimetype>
				<mc:resourceprovider>06c37927-cfec-4624-9b0d-87066d6df559</mc:resourceprovider>
				<mc:relationships></mc:relationships>
				<mc:name>3490A2047482</mc:name>
				<mcs:city required="true" label="City">Helena</mcs:city>
				<mcs:building required="true" label="Building">Mitchell</mcs:building>
				<mcs:model required="true" label="Model"></mcs:model>
				<mcs:firmwareversion required="false" label="Firmware Version"></mcs:firmwareversion>
				<mcs:ip required="true" label="IP Address"></mcs:ip>
				<mcs:category required="false" label="Category">Mainframe External Tape Drives</mcs:category>
				<mc:published>true</mc:published>
			</D:prop>
		</D:set>
	</D:propertyupdate>
	
	*/
	
	function buildPropPatch(specificationId, headerData, lineData) {
		var response = '<?xml version="1.0" encoding="utf-8" ?><D:propertyupdate xmlns:D="DAV:" xmlns:mc="uri://MetaCollection/v2" xmlns:mcs="uri://MetaCollection/v2/spec/' + 
		specificationId + '"><D:set><D:prop><mc:resourceprovider>' + specificationId + '</mc:resourceprovider>';
		
		for (var i=0; i< headerData.length; i++) {
			response = response + createPropNode(headerData[i], lineData[i]);
		}
		
		return response + '</D:prop></D:set></D:propertyupdate>';
	}
	
	function createPropNode(nameString, valueString) {
		// special case, we can set the webdav property of name... all others are on mc namespaces
		if (nameString.toUpperCase() == "DISPLAYNAME")
			return "<d:displayname>" + valueString + "</d:displayname>";
		
		var prefix = "mcs:"
		var name = nameString.toLowerCase();
		
		if (isBasicProperty(name))
			prefix = "mc:"
			
		return "<" + prefix + name + ">" + valueString.replace(/^\s*/, "").replace(/\s*$/, "") + "</" + prefix + name + ">";
	}

	// check with options request to see if the item exists.
	//	if the item does not exist then we need to PUT in a new item and immediately PROPPATCH that item to use our given itemspecification.
	//		if we don't patch the item then the system will complain about properties that do not match the default item type (as set by the PUT)
	function verifyCreateItem(itemUri, itemSpecification) {
		if (isValidUri(itemUri + '?ver=latest'))
			return itemUri;
		else {
			var req = new XMLHttpRequest();
			req.open("PUT", itemUri, false);
			req.send(""); // this is the request body.
			if (req.status >= 200 && req.status <= 399) {// ok or redirect statuses, now we need to immedidately patch the item with the proper item specification id.
				var newUri = req.getResponseHeader("Location");
				var req2 = new XMLHttpRequest();
				req2.open("PROPPATCH", newUri, false);
				req2.send('<?xml version="1.0" encoding="utf-8" ?><D:propertyupdate xmlns:D="DAV:" xmlns:mc="uri://MetaCollection/v2"><D:set><D:prop><mc:resourceprovider>' + itemSpecification + '</mc:resourceprovider></D:prop></D:set></D:propertyupdate>');
				req2.Status;
				return newUri;
			}
			else {
				alert('"' + itemUri + '" appears to be an invalid uri');
				return itemUri; // this uri appears to be invalid
			}
		}
	}
	
	function isValidUri(itemUri) {
		var req = new XMLHttpRequest();
		req.open("OPTIONS", itemUri, false);
		req.send();
		return (req.status >= 200 && req.status <= 399) // ok or redirect statuses
	}
	
	function doValidation() {
		var lines = document.getElementById("csv").value.replace("\r","").split("\n");
		
		if (lines.length < 2) {
			alert('At least two rows are required, a header row and a data row.');
			return;
		}
		
		if (!isValidUri(document.getElementById("destinationFolder").value)) {
			alert('Destination Folder Does Not Exist.');
			return;
		}
		
		if (!document.getElementById("specificationId").value.match(specCheck)) {
			alert('Invalid Specification Id.');
			return;
		}

		nameIndex = -1;
		var headerData = lines[0].split(",");
		var headerValidExp = RegExp(/^[a-zA-Z0-9-_]+$/);
		for (var i=0; i < headerData.length; i++) {
			if (isNotSettableProperty(headerData[i])) {
				alert('Given Property "' + headerData[i] + '" can not be set in a PROPPATH request.');
				return;
			}
			var name = headerData[i].toUpperCase();
			if (name == "NAME" || name == "DISPLAYNAME") {
				nameIndex = i;
			}
			if (!headerData[i].match(headerValidExp)) {
				alert('"' + headerData[i] + '" is not a valid property name.\n Property Names can not contain special chars or spaces');
				return;				
			}
		}
		
		if (nameIndex < 0) {
			alert('One of the given properties to be set must be "NAME" or "DISPLAYNAME" in order to identify each row.');
		}
		
		for (var i=1; i < lines.length; i ++) {
			var lineData = lines[i].split(",");
			if (lineData.length != headerData.length) {
				alert('Row count data mismatch on line ' + (i+1));
				return;
			}
			if (!lineData[nameIndex].match(nameCheck)) {
				alert('Row ' + (i+1) + ' has an invalid value for an item name \n\n"' + lineData[nameIndex] + 
					'"\n\n A Name can not contain spaces and may only consist of numbers,\n letters and the following special chars  !, *, (, ), " ,$ ,- ,_ ,. ,+');
				return;
			}
		}
		alert('Found ' + headerData.length + ' columns of data in ' + (lines.length-1) + ' entries to load');
		document.getElementById("validateButton").disabled = true;
		document.getElementById("uploadButton").disabled = false;
	}
	
	function doUploadRequest() {
	
		var lines = document.getElementById("csv").value.replace("\r","").split("\n");
		var headerData = lines[0].split(',');
		var baseUri = document.getElementById("destinationFolder").value;
	
		for (var i=1; i < lines.length; i ++) {
			var lineData = lines[i].split(",");
			var itemUri = baseUri + '/' + lineData[nameIndex];
			
			itemUri = verifyCreateItem(itemUri, document.getElementById("specificationId").value)
			var req = new XMLHttpRequest();
			output.innerHTML += "Sending Data for  '" + itemUri + "'\n";
			req.open("PROPPATCH", itemUri, false);
			req.send(buildPropPatch(document.getElementById("specificationId").value, headerData, lineData));
			try {
				output.innerHTML += req.status + " " + req.statusText + "\n";
				if (req.responseText.indexOf('HTTP/1.1 403') > 0 || req.responseText.indexOf('HTTP/1.1 424') > 0)
					output.innerHTML += 'SAVE FAILED DUE TO PROPERTY DEPENDENCY -- CHECK THAT YOU ARE NOT SETTING PROPERTIES THAT ARE NOT PART OF THE SPECIFICATION OR USING INVALID VALUES.\n\n';
			} catch(err) {
				output.innerHTML += "Error -- " + req.responseText;
			}
		}
	}


</script>
</html>
